package org.logsin37.tool.eurekamanager.infra.task.runner;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.logsin37.tool.eurekamanager.domain.entity.EurekaApplication;
import org.logsin37.tool.eurekamanager.domain.entity.EurekaInstance;
import org.logsin37.tool.eurekamanager.domain.entity.FrozenConfig;
import org.logsin37.tool.eurekamanager.domain.service.EurekaRemoteDomainService;
import org.logsin37.tool.eurekamanager.domain.service.FrozenDomainService;
import org.logsin37.tool.eurekamanager.infra.constant.ErrorMessages;
import org.logsin37.tool.eurekamanager.infra.constant.EurekaInstanceStatus;
import org.logsin37.tool.eurekamanager.infra.repository.FrozenConfigRepository;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

/**
 * Eureka冻结任务Runner
 *
 * @author Greco 2021-11-23
 */
@Slf4j
public class FrozenTaskRunner implements Runnable{

    private final FrozenConfig frozenConfig;
    private final FrozenConfigRepository frozenConfigRepository;
    private final EurekaRemoteDomainService eurekaRemoteDomainService;
    private final FrozenDomainService frozenDomainService;

    public FrozenTaskRunner(
            FrozenConfig frozenConfig,
            FrozenConfigRepository frozenConfigRepository,
            EurekaRemoteDomainService eurekaRemoteDomainService,
            FrozenDomainService frozenDomainService
    ) {
        this.frozenConfig = frozenConfig;
        this.frozenConfigRepository = frozenConfigRepository;
        this.eurekaRemoteDomainService = eurekaRemoteDomainService;
        this.frozenDomainService = frozenDomainService;
    }

    @Override
    public void run() {
        final String eurekaCode = frozenConfig.getEurekaCode();
        while (true) {
            // 打印日志
            log.info("check and frozen eureka {}", eurekaCode);
            // 检查是否处于冻结状态
            final Boolean isFrozen = Optional.ofNullable(this.frozenConfigRepository.isFrozen(eurekaCode))
                    .map(Mono::block)
                    .orElse(Boolean.FALSE);
            if(!isFrozen) {
                // 未冻结则退出线程执行
                break;
            }
            // 执行比较锁定逻辑
            if(!this.compareAndLock(eurekaCode)) {
                // 如果失败了, 则退出锁定
                this.frozenConfigRepository.dropFrozenRecord(eurekaCode).block();
                log.warn("check and frozen eureka {} fail, unfrozen", eurekaCode);
                break;
            }
            // 10秒扫一次
            ThreadUtil.safeSleep(10000);
        }
        // 退出前清理登记记录
        this.frozenDomainService.unfrozen(eurekaCode).block();
    }

    /**
     * 根据Eureka代码, 对比锁定配置和Application现状, 将不在锁定列表中的app实例踢下线
     *
     * @param eurekaCode Eureka代码
     * @return 是否执行成功
     */
    private boolean compareAndLock(String eurekaCode) {
        // 数据校验
        Assert.hasText(eurekaCode, String.format(ErrorMessages.NOT_NULL, "eureka code"));
        // 获取冻结配置和eureka信息
        final Mono<FrozenConfig> frozenConfigMono = this.frozenConfigRepository.getFrozenConfigByEurekaCode(eurekaCode)
                .publishOn(Schedulers.elastic())
                .subscribeOn(Schedulers.elastic());
        final Flux<EurekaApplication> eurekaApplicationFlux = this.eurekaRemoteDomainService.listAllApplications(eurekaCode)
                .publishOn(Schedulers.elastic())
                .subscribeOn(Schedulers.elastic());
        final FrozenConfig frozenConfig = frozenConfigMono.block();
        final List<EurekaApplication> eurekaApplications = Optional.ofNullable(eurekaApplicationFlux.collectList().block()).orElse(Collections.emptyList());
        // 过滤掉状态不为UP的实例并调整结构为Map
        final List<EurekaApplication> frozenAppList = Optional.ofNullable(frozenConfig).map(FrozenConfig::getApplicationList).orElse(null);
        if(CollectionUtils.isEmpty(frozenAppList)) {
            return false;
        }
        final Map<String, List<EurekaInstance>> appNameToInstancesMap = eurekaApplications.stream()
                .collect(
                Collectors.toMap(
                        EurekaApplication::getName,
                        ea -> Optional.ofNullable(ea.getInstance()).orElse(Collections.emptyList()).stream()
                                .filter(ei -> ei.getStatus() == EurekaInstanceStatus.UP)
                                .collect(Collectors.toList())
                )
        );
        // 遍历App
        for (EurekaApplication frozenApp : frozenAppList) {
            final List<EurekaInstance> realInstances = appNameToInstancesMap.get(frozenApp.getName());
            if(CollectionUtils.isEmpty(realInstances)) {
                continue;
            }
            // 生成该App下的instance规则组
            final List<Pattern> instanceIdRules = Optional.ofNullable(frozenApp.getInstance()).orElse(Collections.emptyList()).stream()
                    .map(EurekaInstance::getInstanceId)
                    .map(instanceIdRule -> StringUtils.isBlank(instanceIdRule) ? ".*" : instanceIdRule)
                    .map(Pattern::compile).collect(Collectors.toList());
            // 遍历App下所有的instance
            for (EurekaInstance realInstance : realInstances) {
                final String realInstanceId = realInstance.getInstanceId();
                if(StringUtils.isBlank(realInstanceId)) {
                    continue;
                }
                final boolean isValidInstance = instanceIdRules.stream().anyMatch(instanceIdRule -> instanceIdRule.matcher(realInstanceId).matches());
                if(!isValidInstance) {
                    // 如果当前instanceId不满足任何一个规则组, 则踢下线
                    this.eurekaRemoteDomainService.putStatus(eurekaCode, realInstance, EurekaInstanceStatus.OUT_OF_SERVICE).block();
                    log.info("{} is not a valid instance and will be out of service", realInstanceId);
                }
            }
        }
        // 完成
        return true;
    }

}
